﻿using System;
using System.Drawing;
using System.Numerics;
using System.Collections.Generic;

namespace Fractals
{
    /// <summary>
    /// a Generator that can generates fractals like Mandelbrot or Julia Sets
    /// 
    /// bits and pieces taken from here
    /// http://rosettacode.org/wiki/Mandelbrot_set
    /// and wikipedia pseudo code on Mandelbrot and Julia Sets
    /// </summary>
    class FractalGenerator
    {
        //for random color mode
        Random m_randGen = new Random();
        List<HSLColor> m_colorArray;
        private double m_maxNorm;
        private double m_maxEscapeRadius;
        private int m_colormode;
        private double m_randomHue;

        #region Properties

        public int MaxRandColors { get; set; }
        public int MaxIterations { get; set; }
        public double MaxEscapeRadius
        {
            set
            {
                m_maxEscapeRadius = value;
                m_maxNorm = value * value;
            }
            get { return m_maxEscapeRadius; }
        }
        public bool SmoothShading { get; set; }
        public int ColorMode
        {
            get { return m_colormode; }
            set {
                m_colormode = value;
                if (value == 3) generateRandomColorArray();
                if (value == 4) generateRandomHue();
            }
        }

        #endregion

        #region Constructor
        public FractalGenerator()
        {
            MaxRandColors = 200;
            MaxIterations = 200;
            MaxEscapeRadius = 2.0;
            SmoothShading = true;
            ColorMode = 2;
            generateRandomColorArray();
            generateRandomHue();
        }
        #endregion

        #region Mandelbrot
        public void GenerateMandelbrotImage(Bitmap bitmap)
        {
            double scale = 2 * m_maxEscapeRadius / Math.Min(bitmap.Width, bitmap.Height);

            for (int y = 0; y < bitmap.Height; y++)
            {
                double imaginary = (y - bitmap.Height / 2) * scale;

                for (int x = 0; x < bitmap.Width; x++)
                {
                    double real = (x - bitmap.Width / 2) * scale;
                    double pixelValue = CalcMandelbrotValue(new Complex(real, imaginary));
                    bitmap.SetPixel(x, y, GetColor(pixelValue));
                }
            }
        }

        private double CalcMandelbrotValue(Complex c)
        {
            // from http://en.wikipedia.org/w/index.php?title=Mandelbrot_set

            int iteration = 0;
            Complex z = new Complex();

            do
            {
                z = z * z + c;
                iteration++;
            } while ((z.Real * z.Real + z.Imaginary * z.Imaginary) < m_maxNorm && iteration < MaxIterations);

            if (iteration < MaxIterations)
            {
                if (SmoothShading)
                {
                    // "Banding can be eliminated by using a fractional escape count.
                    // There are many ways that a fractional escape count can be computed.
                    // One possible way of obtaining a fraction is to measure how far the iterated point landed outside of th escape cutoff.
                    // One of the smoothest, most mathematically correct formulas is the "renormalized" iteration count."
                    // http://linas.org/art-gallery/escape/smooth.html

                    double mu = iteration - Math.Log(Math.Log(Math.Sqrt(z.Real * z.Real + z.Imaginary * z.Imaginary))) / Math.Log(MaxEscapeRadius);
                    return mu / MaxIterations;
                }
                else
                    return (double)iteration / MaxIterations;
            }
            else
                return 0;
        }
        #endregion

        #region Julia Sets

        public void GenerateJuliaSetImage(Bitmap bitmap, Complex c)
        {
            double scale = 2 * MaxEscapeRadius / Math.Min(bitmap.Width, bitmap.Height);
            for (int y = 0; y < bitmap.Height; y++)
            {
                double imaginary = (bitmap.Height / 2 - y) * scale;

                for (int x = 0; x < bitmap.Width; x++)
                {
                    double real = (x - bitmap.Width / 2) * scale;
                    double color = CalcJuliaSetColor(new Complex(real, imaginary), c);

                    bitmap.SetPixel(x, y, GetColor(color));
                }
            }
        }

        private double CalcJuliaSetColor(Complex z, Complex c)
        {
            int iteration = 0;

            do
            {
                z = z * z + c;
                iteration++;
            } while ((z.Real * z.Real + z.Imaginary * z.Imaginary) < m_maxNorm && iteration < MaxIterations);

            if (iteration < MaxIterations)
                return (double)iteration / MaxIterations;
            else
                return 0; // black
        }

        #endregion

        #region Coloring

        private Color GetColor(double value)
        {
            if (value == 0.0)
                return Color.Black;

            var finalColor = new HSLColor(Color.Black);

            switch (ColorMode)
            {
                case 0:
                    value = Math.Pow(value, 0.20);
                    finalColor = Color.FromArgb(10, (int)(value * 255.0), 10);
                    break;
                case 1:
                    value = Math.Pow(value, 0.20);
                    finalColor = Color.FromArgb((int)(value * 255.0),10, 10);
                    break;
                case 2:
                    value = Math.Pow(value, 0.20);
                    finalColor = Color.FromArgb((int)(value * 255.0),(int)(value * 255.0), 5);
                    break;
                case 3:
                    finalColor = m_colorArray[(int)(MaxIterations * value) % MaxRandColors];
                    break;
                case 4:
                    finalColor.Hue = (m_randomHue + value * 255.0) % 255.0;
                    finalColor.Saturation = 0.70 * 255.0;
                    finalColor.Luminosity = 0.45 * 255.0;
                    break;
                case 5:
                    finalColor.Hue = (value * 255.0) % 255.0;
                    finalColor.Saturation = 0.70 * 255.0;
                    finalColor.Luminosity = 0.45 * 255.0;
                    break;
            }

            return finalColor;
        }

        #endregion

        #region helper methods

        /// <summary>
        /// helper method for random colors
        /// </summary>
        private void generateRandomColorArray()
        {
            m_colorArray = new List<HSLColor>();
            //create random colors
            var basehue = m_randGen.Next() % 255.0;

            for (int i = 0; i < MaxRandColors; i++)
            {
                var hue         = (basehue + m_randGen.Next() % 80.0) % 255.0;
                var saturation  = 0.2 + m_randGen.NextDouble() * 0.8;
                var lumi        = 0.4 + m_randGen.NextDouble() * 0.6;

                m_colorArray.Add(new HSLColor(hue, saturation*255.0, lumi*255.0));
                m_colorArray.Sort();
            }
        }

        private void generateRandomHue()
        {
            m_randomHue = m_randGen.NextDouble()*255.0;
        }

        #endregion
    }
}
